﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace EasyMvvm
{
    public sealed class Mediator
    {
        static Mediator instance = new Mediator();
        public static Mediator Instance
        {
            get
            {
                return instance;
            }
        }

        sealed class MediatorMethod
        {
            readonly MethodInfo methodInfo;
            public readonly Type MessageType;
            private ThreadMode threadMode;
            public MediatorMethod(MethodInfo methodInfo, Type messageType, ThreadMode threadMode)
            {
                this.methodInfo = methodInfo;
                this.MessageType = messageType;
                this.threadMode = threadMode;
            }

            public bool Invoke(object target, MediatorMessage message)
            {
                if (threadMode == ThreadMode.Any)
                {
                    methodInfo.Invoke(target, new object[] { message });
                    return true;
                }
                else if (threadMode == ThreadMode.Gui)
                {
                    System.Windows.Threading.Dispatcher dispatcher = System.Windows.Application.Current.Dispatcher;
                    if (dispatcher != null)
                    {
                        dispatcher.Invoke(new Func<object, object[], object>(methodInfo.Invoke), target, new object[] { message });
                        return true;
                    }
                    else
                        return false;
                }
                else if (threadMode == ThreadMode.Background)
                {
                    Func<object, object[], object> action = new Func<object, object[], object>(methodInfo.Invoke);
                    action.BeginInvoke(target, new object[] { message }, null, null);
                    return true;
                }

                throw new NotImplementedException("unhandled thread mode");
            }
        }

        sealed class MediatorMap
        {
            private bool strongReference;
            private object target;
            private WeakReference weakTarget;


            public object Target
            {
                get
                {
                    if (strongReference)
                        return target;
                    else
                    {
                        return weakTarget.Target;
                    }
                }
            }
            public MediatorMethod[] MediatorMethods { get; private set; }


            public MediatorMap(object target, MethodInfo[] methods, bool strongReference)
            {
                this.strongReference = strongReference;
                if (strongReference)
                    this.target = target;
                else
                    weakTarget = new WeakReference(target);

                List<MediatorMethod> methodList = new List<MediatorMethod>();
                foreach (MethodInfo method in methods)
                {
                    MediatorMethodAttribute attribute = (MediatorMethodAttribute)method.GetCustomAttributes(typeof(MediatorMethodAttribute), true)[0];
                    methodList.Add(new MediatorMethod(method, method.GetParameters()[0].ParameterType, attribute.ThreadMode));
                }
                MediatorMethods = methodList.ToArray();
            }

            public int SendMessage(MediatorMessage message)
            {
                object owner = Target;
                if (owner == null)
                    return 0;
                int count = 0;
                foreach (MediatorMethod method in MediatorMethods)
                {
                    if ((method.MessageType == message.GetType() || message.GetType().IsSubclassOf(method.MessageType)) &&
                        method.Invoke(owner, message))
                        count++;
                }

                return count;
            }
        }

        List<MediatorMap> mediatorMaps = new List<MediatorMap>();

        public void Register(object target, bool strongReference)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            List<MethodInfo> methodList = new List<MethodInfo>();

            MethodInfo[] methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            foreach (MethodInfo method in methods)
            {
                MediatorMethodAttribute[] attributes = (MediatorMethodAttribute[])method.GetCustomAttributes(typeof(MediatorMethodAttribute), true);
                if (attributes.Length == 1)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length != 1)
                        throw new ArgumentException("A mediator method can only have one in parameter");

                    if (!parameters[0].ParameterType.IsSubclassOf(typeof(MediatorMessage)) && parameters[0].ParameterType != typeof(MediatorMessage))
                        throw new ArgumentException("Parameter of the mediator method must inherit MediatorMessage");

                    methodList.Add(method);
                }
            }

            if (methodList.Count > 0)
            {
                lock (mediatorMaps)
                {
                    mediatorMaps.Add(new MediatorMap(target, methodList.ToArray(), strongReference));
                }
            }
        }

        public bool Unregister(object target)
        {
            bool removed = false;
            lock (mediatorMaps)
            {
                for (int i = 0; i < mediatorMaps.Count; i++)
                {
                    object targetValue = mediatorMaps[i].Target;

                    if (targetValue == null || targetValue == target)
                    {
                        removed = true;
                        mediatorMaps.RemoveAt(i);
                        i--;
                    }
                }
            }
            return removed;

        }

        public int SendMessage(MediatorMessage message)
        {
            int count = 0;
            lock (mediatorMaps)
            {
                for (int i = 0; i < mediatorMaps.Count; i++)
                {
                    MediatorMap map = mediatorMaps[i];
                    object o = map.Target;
                    if (o == null)
                    {
                        mediatorMaps.RemoveAt(i);
                        i--;
                        continue;
                    }

                    count += map.SendMessage(message);
                }
            }

            return count;
        }
    }
}
